/*
---

script: Core.js

description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.

license: MIT-style license.

copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).

authors: The MooTools production team (http://mootools.net/developers/)

inspiration:
- Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
- Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)

provides: [Mootools, Native, Hash.base, Array.each, $util]

...
*/

var MooTools = {
  'version': '1.2.4',
  'build': '0d9113241a90b9cd5643b926795852a2026710d4'
};

var Native = function(options)
{
  options = options || {};
  var name = options.name;
  var legacy = options.legacy;
  var protect = options.protect;
  var methods = options.implement;
  var generics = options.generics;
  var initialize = options.initialize;
  var afterImplement = options.afterImplement || function() { };
  var object = initialize || legacy;
  generics = generics !== false;

  object.constructor = Native;
  object.$family = { name: 'native' };
  if (legacy && initialize) object.prototype = legacy.prototype;
  object.prototype.constructor = object;

  if (name)
  {
    var family = name.toLowerCase();
    object.prototype.$family = { name: family };
    Native.typize(object, family);
  }

  var add = function(obj, name, method, force)
  {
    if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
    if (generics) Native.genericize(obj, name, protect);
    afterImplement.call(obj, name, method);
    return obj;
  };

  object.alias = function(a1, a2, a3)
  {
    if (typeof a1 == 'string')
    {
      var pa1 = this.prototype[a1];
      if ((a1 = pa1)) return add(this, a2, a1, a3);
    }
    for (var a in a1) this.alias(a, a1[a], a2);
    return this;
  };

  object.implement = function(a1, a2, a3)
  {
    if (typeof a1 == 'string') return add(this, a1, a2, a3);
    for (var p in a1) add(this, p, a1[p], a2);
    return this;
  };

  if (methods) object.implement(methods);

  return object;
};

Native.genericize = function(object, property, check)
{
  if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function()
  {
    var args = Array.prototype.slice.call(arguments);
    return object.prototype[property].apply(args.shift(), args);
  };
};

Native.implement = function(objects, properties)
{
  for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
};

Native.typize = function(object, family)
{
  if (!object.type) object.type = function(item)
  {
    return ($type(item) === family);
  };
};

(function()
{
  var natives = { 'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String };
  for (var n in natives) new Native({ name: n, initialize: natives[n], protect: true });

  var types = { 'boolean': Boolean, 'native': Native, 'object': Object };
  for (var t in types) Native.typize(types[t], t);

  var generics = {
    'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
    'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
  };
  for (var g in generics)
  {
    for (var i = generics[g].length; i--; ) Native.genericize(natives[g], generics[g][i], true);
  }
})();

var Hash = new Native({

  name: 'Hash',

  initialize: function(object)
  {
    if ($type(object) == 'hash') object = $unlink(object.getClean());
    for (var key in object) this[key] = object[key];
    return this;
  }

});

Hash.implement({

  forEach: function(fn, bind)
  {
    for (var key in this)
    {
      if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
    }
  },

  getClean: function()
  {
    var clean = {};
    for (var key in this)
    {
      if (this.hasOwnProperty(key)) clean[key] = this[key];
    }
    return clean;
  },

  getLength: function()
  {
    var length = 0;
    for (var key in this)
    {
      if (this.hasOwnProperty(key)) length++;
    }
    return length;
  }

});

Hash.alias('forEach', 'each');

Array.implement({

  forEach: function(fn, bind)
  {
    for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
  }

});

Array.alias('forEach', 'each');

function $A(iterable)
{
  if (iterable.item)
  {
    var l = iterable.length, array = new Array(l);
    while (l--) array[l] = iterable[l];
    return array;
  }
  return Array.prototype.slice.call(iterable);
};

function $arguments(i)
{
  return function()
  {
    return arguments[i];
  };
};

function $chk(obj)
{
  return !!(obj || obj === 0);
};

function $clear(timer)
{
  clearTimeout(timer);
  clearInterval(timer);
  return null;
};

function $defined(obj)
{
  return (obj != undefined);
};

function $each(iterable, fn, bind)
{
  var type = $type(iterable);
  ((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
};

function $empty() { };

function $extend(original, extended)
{
  for (var key in (extended || {})) original[key] = extended[key];
  return original;
};

function $H(object)
{
  return new Hash(object);
};

function $lambda(value)
{
  return ($type(value) == 'function') ? value : function()
  {
    return value;
  };
};

function $merge()
{
  var args = Array.slice(arguments);
  args.unshift({});
  return $mixin.apply(null, args);
};

function $mixin(mix)
{
  for (var i = 1, l = arguments.length; i < l; i++)
  {
    var object = arguments[i];
    if ($type(object) != 'object') continue;
    for (var key in object)
    {
      var op = object[key], mp = mix[key];
      mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
    }
  }
  return mix;
};

function $pick()
{
  for (var i = 0, l = arguments.length; i < l; i++)
  {
    if (arguments[i] != undefined) return arguments[i];
  }
  return null;
};

function $random(min, max)
{
  return Math.floor(Math.random() * (max - min + 1) + min);
};

function $splat(obj)
{
  var type = $type(obj);
  return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
};

var $time = Date.now || function()
{
  return +new Date;
};

function $try()
{
  for (var i = 0, l = arguments.length; i < l; i++)
  {
    try
    {
      return arguments[i]();
    } catch (e) { }
  }
  return null;
};

function $type(obj)
{
  if (obj == undefined) return false;
  if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
  if (obj.nodeName)
  {
    switch (obj.nodeType)
    {
      case 1: return 'element';
      case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
    }
  } else if (typeof obj.length == 'number')
  {
    if (obj.callee) return 'arguments';
    else if (obj.item) return 'collection';
  }
  return typeof obj;
};

function $unlink(object)
{
  var unlinked;
  switch ($type(object))
  {
    case 'object':
      unlinked = {};
      for (var p in object) unlinked[p] = $unlink(object[p]);
      break;
    case 'hash':
      unlinked = new Hash(object);
      break;
    case 'array':
      unlinked = [];
      for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
      break;
    default: return object;
  }
  return unlinked;
};


/*
---

script: Browser.js

description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.

license: MIT-style license.

requires: 
- /Native
- /$util

provides: [Browser, Window, Document, $exec]

...
*/

var Browser = $merge({

  Engine: { name: 'unknown', version: 0 },

  Platform: { name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase() },

  Features: { xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector) },

  Plugins: {},

  Engines: {

    presto: function()
    {
      return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
    },

    trident: function()
    {
      return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
    },

    webkit: function()
    {
      return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
    },

    gecko: function()
    {
      return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
    }

  }

}, Browser || {});

Browser.Platform[Browser.Platform.name] = true;

Browser.detect = function()
{

  for (var engine in this.Engines)
  {
    var version = this.Engines[engine]();
    if (version)
    {
      this.Engine = { name: engine, version: version };
      this.Engine[engine] = this.Engine[engine + version] = true;
      break;
    }
  }

  return { name: engine, version: version };

};

Browser.detect();

Browser.Request = function()
{
  return $try(function()
  {
    return new XMLHttpRequest();
  }, function()
  {
    return new ActiveXObject('MSXML2.XMLHTTP');
  }, function()
  {
    return new ActiveXObject('Microsoft.XMLHTTP');
  });
};

Browser.Features.xhr = !!(Browser.Request());

Browser.Plugins.Flash = (function()
{
  var version = ($try(function()
  {
    return navigator.plugins['Shockwave Flash'].description;
  }, function()
  {
    return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
  }) || '0 r0').match(/\d+/g);
  return { version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0 };
})();

function $exec(text)
{
  if (!text) return text;
  if (window.execScript)
  {
    window.execScript(text);
  } else
  {
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
    document.head.appendChild(script);
    document.head.removeChild(script);
  }
  return text;
};

Native.UID = 1;

var $uid = (Browser.Engine.trident) ? function(item)
{
  return (item.uid || (item.uid = [Native.UID++]))[0];
} : function(item)
{
  return item.uid || (item.uid = Native.UID++);
};

var Window = new Native({

  name: 'Window',

  legacy: (Browser.Engine.trident) ? null : window.Window,

  initialize: function(win)
  {
    $uid(win);
    if (!win.Element)
    {
      win.Element = $empty;
      if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
      win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
    }
    win.document.window = win;
    return $extend(win, Window.Prototype);
  },

  afterImplement: function(property, value)
  {
    window[property] = Window.Prototype[property] = value;
  }

});

Window.Prototype = { $family: { name: 'window'} };

new Window(window);

var Document = new Native({

  name: 'Document',

  legacy: (Browser.Engine.trident) ? null : window.Document,

  initialize: function(doc)
  {
    $uid(doc);
    doc.head = doc.getElementsByTagName('head')[0];
    doc.html = doc.getElementsByTagName('html')[0];
    if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function()
    {
      doc.execCommand("BackgroundImageCache", false, true);
    });
    if (Browser.Engine.trident) doc.window.attachEvent('onunload', function()
    {
      doc.window.detachEvent('onunload', arguments.callee);
      doc.head = doc.html = doc.window = null;
    });
    return $extend(doc, Document.Prototype);
  },

  afterImplement: function(property, value)
  {
    document[property] = Document.Prototype[property] = value;
  }

});

Document.Prototype = { $family: { name: 'document'} };

new Document(document);


/*
---

script: Array.js

description: Contains Array Prototypes like each, contains, and erase.

license: MIT-style license.

requires:
- /$util
- /Array.each

provides: [Array]

...
*/

Array.implement({

  every: function(fn, bind)
  {
    for (var i = 0, l = this.length; i < l; i++)
    {
      if (!fn.call(bind, this[i], i, this)) return false;
    }
    return true;
  },

  filter: function(fn, bind)
  {
    var results = [];
    for (var i = 0, l = this.length; i < l; i++)
    {
      if (fn.call(bind, this[i], i, this)) results.push(this[i]);
    }
    return results;
  },

  clean: function()
  {
    return this.filter($defined);
  },

  indexOf: function(item, from)
  {
    var len = this.length;
    for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++)
    {
      if (this[i] === item) return i;
    }
    return -1;
  },

  map: function(fn, bind)
  {
    var results = [];
    for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
    return results;
  },

  some: function(fn, bind)
  {
    for (var i = 0, l = this.length; i < l; i++)
    {
      if (fn.call(bind, this[i], i, this)) return true;
    }
    return false;
  },

  associate: function(keys)
  {
    var obj = {}, length = Math.min(this.length, keys.length);
    for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
    return obj;
  },

  link: function(object)
  {
    var result = {};
    for (var i = 0, l = this.length; i < l; i++)
    {
      for (var key in object)
      {
        if (object[key](this[i]))
        {
          result[key] = this[i];
          delete object[key];
          break;
        }
      }
    }
    return result;
  },

  contains: function(item, from)
  {
    return this.indexOf(item, from) != -1;
  },

  extend: function(array)
  {
    for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
    return this;
  },

  getLast: function()
  {
    return (this.length) ? this[this.length - 1] : null;
  },

  getRandom: function()
  {
    return (this.length) ? this[$random(0, this.length - 1)] : null;
  },

  include: function(item)
  {
    if (!this.contains(item)) this.push(item);
    return this;
  },

  combine: function(array)
  {
    for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
    return this;
  },

  erase: function(item)
  {
    for (var i = this.length; i--; i)
    {
      if (this[i] === item) this.splice(i, 1);
    }
    return this;
  },

  empty: function()
  {
    this.length = 0;
    return this;
  },

  flatten: function()
  {
    var array = [];
    for (var i = 0, l = this.length; i < l; i++)
    {
      var type = $type(this[i]);
      if (!type) continue;
      array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
    }
    return array;
  },

  hexToRgb: function(array)
  {
    if (this.length != 3) return null;
    var rgb = this.map(function(value)
    {
      if (value.length == 1) value += value;
      return value.toInt(16);
    });
    return (array) ? rgb : 'rgb(' + rgb + ')';
  },

  rgbToHex: function(array)
  {
    if (this.length < 3) return null;
    if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
    var hex = [];
    for (var i = 0; i < 3; i++)
    {
      var bit = (this[i] - 0).toString(16);
      hex.push((bit.length == 1) ? '0' + bit : bit);
    }
    return (array) ? hex : '#' + hex.join('');
  }

});


/*
---

script: Function.js

description: Contains Function Prototypes like create, bind, pass, and delay.

license: MIT-style license.

requires:
- /Native
- /$util

provides: [Function]

...
*/

Function.implement({

  extend: function(properties)
  {
    for (var property in properties) this[property] = properties[property];
    return this;
  },

  create: function(options)
  {
    var self = this;
    options = options || {};
    return function(event)
    {
      var args = options.arguments;
      args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
      if (options.event) args = [event || window.event].extend(args);
      var returns = function()
      {
        return self.apply(options.bind || null, args);
      };
      if (options.delay) return setTimeout(returns, options.delay);
      if (options.periodical) return setInterval(returns, options.periodical);
      if (options.attempt) return $try(returns);
      return returns();
    };
  },

  run: function(args, bind)
  {
    return this.apply(bind, $splat(args));
  },

  pass: function(args, bind)
  {
    return this.create({ bind: bind, arguments: args });
  },

  bind: function(bind, args)
  {
    return this.create({ bind: bind, arguments: args });
  },

  bindWithEvent: function(bind, args)
  {
    return this.create({ bind: bind, arguments: args, event: true });
  },

  attempt: function(args, bind)
  {
    return this.create({ bind: bind, arguments: args, attempt: true })();
  },

  delay: function(delay, bind, args)
  {
    return this.create({ bind: bind, arguments: args, delay: delay })();
  },

  periodical: function(periodical, bind, args)
  {
    return this.create({ bind: bind, arguments: args, periodical: periodical })();
  }

});


/*
---

script: Number.js

description: Contains Number Prototypes like limit, round, times, and ceil.

license: MIT-style license.

requires:
- /Native
- /$util

provides: [Number]

...
*/

Number.implement({

  limit: function(min, max)
  {
    return Math.min(max, Math.max(min, this));
  },

  round: function(precision)
  {
    precision = Math.pow(10, precision || 0);
    return Math.round(this * precision) / precision;
  },

  times: function(fn, bind)
  {
    for (var i = 0; i < this; i++) fn.call(bind, i, this);
  },

  toFloat: function()
  {
    return parseFloat(this);
  },

  toInt: function(base)
  {
    return parseInt(this, base || 10);
  }

});

Number.alias('times', 'each');

(function(math)
{
  var methods = {};
  math.each(function(name)
  {
    if (!Number[name]) methods[name] = function()
    {
      return Math[name].apply(null, [this].concat($A(arguments)));
    };
  });
  Number.implement(methods);
})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);


/*
---

script: String.js

description: Contains String Prototypes like camelCase, capitalize, test, and toInt.

license: MIT-style license.

requires:
- /Native

provides: [String]

...
*/

String.implement({

  test: function(regex, params)
  {
    return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
  },

  contains: function(string, separator)
  {
    return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
  },

  trim: function()
  {
    return this.replace(/^\s+|\s+$/g, '');
  },

  clean: function()
  {
    return this.replace(/\s+/g, ' ').trim();
  },

  camelCase: function()
  {
    return this.replace(/-\D/g, function(match)
    {
      return match.charAt(1).toUpperCase();
    });
  },

  hyphenate: function()
  {
    return this.replace(/[A-Z]/g, function(match)
    {
      return ('-' + match.charAt(0).toLowerCase());
    });
  },

  capitalize: function()
  {
    return this.replace(/\b[a-z]/g, function(match)
    {
      return match.toUpperCase();
    });
  },

  escapeRegExp: function()
  {
    return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
  },

  toInt: function(base)
  {
    return parseInt(this, base || 10);
  },

  toFloat: function()
  {
    return parseFloat(this);
  },

  hexToRgb: function(array)
  {
    var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
    return (hex) ? hex.slice(1).hexToRgb(array) : null;
  },

  rgbToHex: function(array)
  {
    var rgb = this.match(/\d{1,3}/g);
    return (rgb) ? rgb.rgbToHex(array) : null;
  },

  stripScripts: function(option)
  {
    var scripts = '';
    var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function()
    {
      scripts += arguments[1] + '\n';
      return '';
    });
    if (option === true) $exec(scripts);
    else if ($type(option) == 'function') option(scripts, text);
    return text;
  },

  substitute: function(object, regexp)
  {
    return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name)
    {
      if (match.charAt(0) == '\\') return match.slice(1);
      return (object[name] != undefined) ? object[name] : '';
    });
  }

});


/*
---

script: Hash.js

description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.

license: MIT-style license.

requires:
- /Hash.base

provides: [Hash]

...
*/

Hash.implement({

  has: Object.prototype.hasOwnProperty,

  keyOf: function(value)
  {
    for (var key in this)
    {
      if (this.hasOwnProperty(key) && this[key] === value) return key;
    }
    return null;
  },

  hasValue: function(value)
  {
    return (Hash.keyOf(this, value) !== null);
  },

  extend: function(properties)
  {
    Hash.each(properties || {}, function(value, key)
    {
      Hash.set(this, key, value);
    }, this);
    return this;
  },

  combine: function(properties)
  {
    Hash.each(properties || {}, function(value, key)
    {
      Hash.include(this, key, value);
    }, this);
    return this;
  },

  erase: function(key)
  {
    if (this.hasOwnProperty(key)) delete this[key];
    return this;
  },

  get: function(key)
  {
    return (this.hasOwnProperty(key)) ? this[key] : null;
  },

  set: function(key, value)
  {
    if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
    return this;
  },

  empty: function()
  {
    Hash.each(this, function(value, key)
    {
      delete this[key];
    }, this);
    return this;
  },

  include: function(key, value)
  {
    if (this[key] == undefined) this[key] = value;
    return this;
  },

  map: function(fn, bind)
  {
    var results = new Hash;
    Hash.each(this, function(value, key)
    {
      results.set(key, fn.call(bind, value, key, this));
    }, this);
    return results;
  },

  filter: function(fn, bind)
  {
    var results = new Hash;
    Hash.each(this, function(value, key)
    {
      if (fn.call(bind, value, key, this)) results.set(key, value);
    }, this);
    return results;
  },

  every: function(fn, bind)
  {
    for (var key in this)
    {
      if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
    }
    return true;
  },

  some: function(fn, bind)
  {
    for (var key in this)
    {
      if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
    }
    return false;
  },

  getKeys: function()
  {
    var keys = [];
    Hash.each(this, function(value, key)
    {
      keys.push(key);
    });
    return keys;
  },

  getValues: function()
  {
    var values = [];
    Hash.each(this, function(value)
    {
      values.push(value);
    });
    return values;
  },

  toQueryString: function(base)
  {
    var queryString = [];
    Hash.each(this, function(value, key)
    {
      if (base) key = base + '[' + key + ']';
      var result;
      switch ($type(value))
      {
        case 'object': result = Hash.toQueryString(value, key); break;
        case 'array':
          var qs = {};
          value.each(function(val, i)
          {
            qs[i] = val;
          });
          result = Hash.toQueryString(qs, key);
          break;
        default: result = key + '=' + encodeURIComponent(value);
      }
      if (value != undefined) queryString.push(result);
    });

    return queryString.join('&');
  }

});

Hash.alias({ keyOf: 'indexOf', hasValue: 'contains' });


/*
---

script: Event.js

description: Contains the Event Class, to make the event object cross-browser.

license: MIT-style license.

requires:
- /Window
- /Document
- /Hash
- /Array
- /Function
- /String

provides: [Event]

...
*/

var Event = new Native({

  name: 'Event',

  initialize: function(event, win)
  {
    win = win || window;
    var doc = win.document;
    event = event || win.event;
    if (event.$extended) return event;
    this.$extended = true;
    var type = event.type;
    var target = event.target || event.srcElement;
    while (target && target.nodeType == 3) target = target.parentNode;

    if (type.test(/key/))
    {
      var code = event.which || event.keyCode;
      var key = Event.Keys.keyOf(code);
      if (type == 'keydown')
      {
        var fKey = code - 111;
        if (fKey > 0 && fKey < 13) key = 'f' + fKey;
      }
      key = key || String.fromCharCode(code).toLowerCase();
    } else if (type.match(/(click|mouse|menu)/i))
    {
      doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
      var page = {
        x: event.pageX || event.clientX + doc.scrollLeft,
        y: event.pageY || event.clientY + doc.scrollTop
      };
      var client = {
        x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
        y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
      };
      if (type.match(/DOMMouseScroll|mousewheel/))
      {
        var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
      }
      var rightClick = (event.which == 3) || (event.button == 2);
      var related = null;
      if (type.match(/over|out/))
      {
        switch (type)
        {
          case 'mouseover': related = event.relatedTarget || event.fromElement; break;
          case 'mouseout': related = event.relatedTarget || event.toElement;
        }
        if (!(function()
        {
          while (related && related.nodeType == 3) related = related.parentNode;
          return true;
        }).create({ attempt: Browser.Engine.gecko })()) related = false;
      }
    }

    return $extend(this, {
      event: event,
      type: type,

      page: page,
      client: client,
      rightClick: rightClick,

      wheel: wheel,

      relatedTarget: related,
      target: target,

      code: code,
      key: key,

      shift: event.shiftKey,
      control: event.ctrlKey,
      alt: event.altKey,
      meta: event.metaKey
    });
  }

});

Event.Keys = new Hash({
  'enter': 13,
  'up': 38,
  'down': 40,
  'left': 37,
  'right': 39,
  'esc': 27,
  'space': 32,
  'backspace': 8,
  'tab': 9,
  'delete': 46
});

Event.implement({

  stop: function()
  {
    return this.stopPropagation().preventDefault();
  },

  stopPropagation: function()
  {
    if (this.event.stopPropagation) this.event.stopPropagation();
    else this.event.cancelBubble = true;
    return this;
  },

  preventDefault: function()
  {
    if (this.event.preventDefault) this.event.preventDefault();
    else this.event.returnValue = false;
    return this;
  }

});


/*
---

script: Element.js

description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.

license: MIT-style license.

requires:
- /Window
- /Document
- /Array
- /String
- /Function
- /Number
- /Hash

provides: [Element, Elements, $, $$, Iframe]

...
*/

var Element = new Native({

  name: 'Element',

  legacy: window.Element,

  initialize: function(tag, props)
  {
    var konstructor = Element.Constructors.get(tag);
    if (konstructor) return konstructor(props);
    if (typeof tag == 'string') return document.newElement(tag, props);
    return document.id(tag).set(props);
  },

  afterImplement: function(key, value)
  {
    Element.Prototype[key] = value;
    if (Array[key]) return;
    Elements.implement(key, function()
    {
      var items = [], elements = true;
      for (var i = 0, j = this.length; i < j; i++)
      {
        var returns = this[i][key].apply(this[i], arguments);
        items.push(returns);
        if (elements) elements = ($type(returns) == 'element');
      }
      return (elements) ? new Elements(items) : items;
    });
  }

});

Element.Prototype = { $family: { name: 'element'} };

Element.Constructors = new Hash;

var IFrame = new Native({

  name: 'IFrame',

  generics: false,

  initialize: function()
  {
    var params = Array.link(arguments, { properties: Object.type, iframe: $defined });
    var props = params.properties || {};
    var iframe = document.id(params.iframe);
    var onload = props.onload || $empty;
    delete props.onload;
    props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time());
    iframe = new Element(iframe || 'iframe', props);
    var onFrameLoad = function()
    {
      var host = $try(function()
      {
        return iframe.contentWindow.location.host;
      });
      if (!host || host == window.location.host)
      {
        var win = new Window(iframe.contentWindow);
        new Document(iframe.contentWindow.document);
        $extend(win.Element.prototype, Element.Prototype);
      }
      onload.call(iframe.contentWindow, iframe.contentWindow.document);
    };
    var contentWindow = $try(function()
    {
      return iframe.contentWindow;
    });
    ((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
    return iframe;
  }

});

var Elements = new Native({

  initialize: function(elements, options)
  {
    options = $extend({ ddup: true, cash: true }, options);
    elements = elements || [];
    if (options.ddup || options.cash)
    {
      var uniques = {}, returned = [];
      for (var i = 0, l = elements.length; i < l; i++)
      {
        var el = document.id(elements[i], !options.cash);
        if (options.ddup)
        {
          if (uniques[el.uid]) continue;
          uniques[el.uid] = true;
        }
        if (el) returned.push(el);
      }
      elements = returned;
    }
    return (options.cash) ? $extend(elements, this) : elements;
  }

});

Elements.implement({

  filter: function(filter, bind)
  {
    if (!filter) return this;
    return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item)
    {
      return item.match(filter);
    } : filter, bind));
  }

});

Document.implement({

  newElement: function(tag, props)
  {
    if (Browser.Engine.trident && props)
    {
      ['name', 'type', 'checked'].each(function(attribute)
      {
        if (!props[attribute]) return;
        tag += ' ' + attribute + '="' + props[attribute] + '"';
        if (attribute != 'checked') delete props[attribute];
      });
      tag = '<' + tag + '>';
    }
    return document.id(this.createElement(tag)).set(props);
  },

  newTextNode: function(text)
  {
    return this.createTextNode(text);
  },

  getDocument: function()
  {
    return this;
  },

  getWindow: function()
  {
    return this.window;
  },

  id: (function()
  {

    var types = {

      string: function(id, nocash, doc)
      {
        id = doc.getElementById(id);
        return (id) ? types.element(id, nocash) : null;
      },

      element: function(el, nocash)
      {
        $uid(el);
        if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName))
        {
          var proto = Element.Prototype;
          for (var p in proto) el[p] = proto[p];
        };
        return el;
      },

      object: function(obj, nocash, doc)
      {
        if (obj.toElement) return types.element(obj.toElement(doc), nocash);
        return null;
      }

    };

    types.textnode = types.whitespace = types.window = types.document = $arguments(0);

    return function(el, nocash, doc)
    {
      if (el && el.$family && el.uid) return el;
      var type = $type(el);
      return (types[type]) ? types[type](el, nocash, doc || document) : null;
    };

  })()

});

if (window.$ == null) Window.implement({
  $: function(el, nc)
  {
    return document.id(el, nc, this.document);
  }
});

Window.implement({

  $$: function(selector)
  {
    if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
    var elements = [];
    var args = Array.flatten(arguments);
    for (var i = 0, l = args.length; i < l; i++)
    {
      var item = args[i];
      switch ($type(item))
      {
        case 'element': elements.push(item); break;
        case 'string': elements.extend(this.document.getElements(item, true));
      }
    }
    return new Elements(elements);
  },

  getDocument: function()
  {
    return this.document;
  },

  getWindow: function()
  {
    return this;
  }

});

Native.implement([Element, Document], {

  getElement: function(selector, nocash)
  {
    return document.id(this.getElements(selector, true)[0] || null, nocash);
  },

  getElements: function(tags, nocash)
  {
    tags = tags.split(',');
    var elements = [];
    var ddup = (tags.length > 1);
    tags.each(function(tag)
    {
      var partial = this.getElementsByTagName(tag.trim());
      (ddup) ? elements.extend(partial) : elements = partial;
    }, this);
    return new Elements(elements, { ddup: ddup, cash: !nocash });
  }

});

(function()
{

  var collected = {}, storage = {};
  var props = { input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value' };

  var get = function(uid)
  {
    return (storage[uid] || (storage[uid] = {}));
  };

  var clean = function(item, retain)
  {
    if (!item) return;
    var uid = item.uid;
    if (Browser.Engine.trident)
    {
      if (item.clearAttributes)
      {
        var clone = retain && item.cloneNode(false);
        item.clearAttributes();
        if (clone) item.mergeAttributes(clone);
      } else if (item.removeEvents)
      {
        item.removeEvents();
      }
      if ((/object/i).test(item.tagName))
      {
        for (var p in item)
        {
          if (typeof item[p] == 'function') item[p] = $empty;
        }
        Element.dispose(item);
      }
    }
    if (!uid) return;
    collected[uid] = storage[uid] = null;
  };

  var purge = function()
  {
    Hash.each(collected, clean);
    if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
    if (window.CollectGarbage) CollectGarbage();
    collected = storage = null;
  };

  var walk = function(element, walk, start, match, all, nocash)
  {
    var el = element[start || walk];
    var elements = [];
    while (el)
    {
      if (el.nodeType == 1 && (!match || Element.match(el, match)))
      {
        if (!all) return document.id(el, nocash);
        elements.push(el);
      }
      el = el[walk];
    }
    return (all) ? new Elements(elements, { ddup: false, cash: !nocash }) : null;
  };

  var attributes = {
    'html': 'innerHTML',
    'class': 'className',
    'for': 'htmlFor',
    'defaultValue': 'defaultValue',
    'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
  };
  var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
  var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];

  bools = bools.associate(bools);

  Hash.extend(attributes, bools);
  Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));

  var inserters = {

    before: function(context, element)
    {
      if (element.parentNode) element.parentNode.insertBefore(context, element);
    },

    after: function(context, element)
    {
      if (!element.parentNode) return;
      var next = element.nextSibling;
      (next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
    },

    bottom: function(context, element)
    {
      element.appendChild(context);
    },

    top: function(context, element)
    {
      var first = element.firstChild;
      (first) ? element.insertBefore(context, first) : element.appendChild(context);
    }

  };

  inserters.inside = inserters.bottom;

  Hash.each(inserters, function(inserter, where)
  {

    where = where.capitalize();

    Element.implement('inject' + where, function(el)
    {
      inserter(this, document.id(el, true));
      return this;
    });

    Element.implement('grab' + where, function(el)
    {
      inserter(document.id(el, true), this);
      return this;
    });

  });

  Element.implement({

    set: function(prop, value)
    {
      switch ($type(prop))
      {
        case 'object':
          for (var p in prop) this.set(p, prop[p]);
          break;
        case 'string':
          var property = Element.Properties.get(prop);
          (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
      }
      return this;
    },

    get: function(prop)
    {
      var property = Element.Properties.get(prop);
      return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
    },

    erase: function(prop)
    {
      var property = Element.Properties.get(prop);
      (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
      return this;
    },

    setProperty: function(attribute, value)
    {
      var key = attributes[attribute];
      if (value == undefined) return this.removeProperty(attribute);
      if (key && bools[attribute]) value = !!value;
      (key) ? this[key] = value : this.setAttribute(attribute, '' + value);
      return this;
    },

    setProperties: function(attributes)
    {
      for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
      return this;
    },

    getProperty: function(attribute)
    {
      var key = attributes[attribute];
      var value = (key) ? this[key] : this.getAttribute(attribute, 2);
      return (bools[attribute]) ? !!value : (key) ? value : value || null;
    },

    getProperties: function()
    {
      var args = $A(arguments);
      return args.map(this.getProperty, this).associate(args);
    },

    removeProperty: function(attribute)
    {
      var key = attributes[attribute];
      (key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
      return this;
    },

    removeProperties: function()
    {
      Array.each(arguments, this.removeProperty, this);
      return this;
    },

    hasClass: function(className)
    {
      return this.className.contains(className, ' ');
    },

    addClass: function(className)
    {
      if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
      return this;
    },

    removeClass: function(className)
    {
      this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
      return this;
    },

    toggleClass: function(className)
    {
      return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
    },

    adopt: function()
    {
      Array.flatten(arguments).each(function(element)
      {
        element = document.id(element, true);
        if (element) this.appendChild(element);
      }, this);
      return this;
    },

    appendText: function(text, where)
    {
      return this.grab(this.getDocument().newTextNode(text), where);
    },

    grab: function(el, where)
    {
      inserters[where || 'bottom'](document.id(el, true), this);
      return this;
    },

    inject: function(el, where)
    {
      inserters[where || 'bottom'](this, document.id(el, true));
      return this;
    },

    replaces: function(el)
    {
      el = document.id(el, true);
      el.parentNode.replaceChild(this, el);
      return this;
    },

    wraps: function(el, where)
    {
      el = document.id(el, true);
      return this.replaces(el).grab(el, where);
    },

    getPrevious: function(match, nocash)
    {
      return walk(this, 'previousSibling', null, match, false, nocash);
    },

    getAllPrevious: function(match, nocash)
    {
      return walk(this, 'previousSibling', null, match, true, nocash);
    },

    getNext: function(match, nocash)
    {
      return walk(this, 'nextSibling', null, match, false, nocash);
    },

    getAllNext: function(match, nocash)
    {
      return walk(this, 'nextSibling', null, match, true, nocash);
    },

    getFirst: function(match, nocash)
    {
      return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
    },

    getLast: function(match, nocash)
    {
      return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
    },

    getParent: function(match, nocash)
    {
      return walk(this, 'parentNode', null, match, false, nocash);
    },

    getParents: function(match, nocash)
    {
      return walk(this, 'parentNode', null, match, true, nocash);
    },

    getSiblings: function(match, nocash)
    {
      return this.getParent().getChildren(match, nocash).erase(this);
    },

    getChildren: function(match, nocash)
    {
      return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
    },

    getWindow: function()
    {
      return this.ownerDocument.window;
    },

    getDocument: function()
    {
      return this.ownerDocument;
    },

    getElementById: function(id, nocash)
    {
      var el = this.ownerDocument.getElementById(id);
      if (!el) return null;
      for (var parent = el.parentNode; parent != this; parent = parent.parentNode)
      {
        if (!parent) return null;
      }
      return document.id(el, nocash);
    },

    getSelected: function()
    {
      return new Elements($A(this.options).filter(function(option)
      {
        return option.selected;
      }));
    },

    getComputedStyle: function(property)
    {
      if (this.currentStyle) return this.currentStyle[property.camelCase()];
      var computed = this.getDocument().defaultView.getComputedStyle(this, null);
      return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
    },

    toQueryString: function()
    {
      var queryString = [];
      this.getElements('input, select, textarea', true).each(function(el)
      {
        if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
        var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt)
        {
          return opt.value;
        }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
        $splat(value).each(function(val)
        {
          if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
        });
      });
      return queryString.join('&');
    },

    clone: function(contents, keepid)
    {
      contents = contents !== false;
      var clone = this.cloneNode(contents);
      var clean = function(node, element)
      {
        if (!keepid) node.removeAttribute('id');
        if (Browser.Engine.trident)
        {
          node.clearAttributes();
          node.mergeAttributes(element);
          node.removeAttribute('uid');
          if (node.options)
          {
            var no = node.options, eo = element.options;
            for (var j = no.length; j--; ) no[j].selected = eo[j].selected;
          }
        }
        var prop = props[element.tagName.toLowerCase()];
        if (prop && element[prop]) node[prop] = element[prop];
      };

      if (contents)
      {
        var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
        for (var i = ce.length; i--; ) clean(ce[i], te[i]);
      }

      clean(clone, this);
      return document.id(clone);
    },

    destroy: function()
    {
      Element.empty(this);
      Element.dispose(this);
      clean(this, true);
      return null;
    },

    empty: function()
    {
      $A(this.childNodes).each(function(node)
      {
        Element.destroy(node);
      });
      return this;
    },

    dispose: function()
    {
      return (this.parentNode) ? this.parentNode.removeChild(this) : this;
    },

    hasChild: function(el)
    {
      el = document.id(el, true);
      if (!el) return false;
      if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
      return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
    },

    match: function(tag)
    {
      return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
    }

  });

  Native.implement([Element, Window, Document], {

    addListener: function(type, fn)
    {
      if (type == 'unload')
      {
        var old = fn, self = this;
        fn = function()
        {
          self.removeListener('unload', fn);
          old();
        };
      } else
      {
        collected[this.uid] = this;
      }
      if (this.addEventListener) this.addEventListener(type, fn, false);
      else this.attachEvent('on' + type, fn);
      return this;
    },

    removeListener: function(type, fn)
    {
      if (this.removeEventListener) this.removeEventListener(type, fn, false);
      else this.detachEvent('on' + type, fn);
      return this;
    },

    retrieve: function(property, dflt)
    {
      var storage = get(this.uid), prop = storage[property];
      if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
      return $pick(prop);
    },

    store: function(property, value)
    {
      var storage = get(this.uid);
      storage[property] = value;
      return this;
    },

    eliminate: function(property)
    {
      var storage = get(this.uid);
      delete storage[property];
      return this;
    }

  });

  window.addListener('unload', purge);

})();

Element.Properties = new Hash;

Element.Properties.style = {

  set: function(style)
  {
    this.style.cssText = style;
  },

  get: function()
  {
    return this.style.cssText;
  },

  erase: function()
  {
    this.style.cssText = '';
  }

};

Element.Properties.tag = {

  get: function()
  {
    return this.tagName.toLowerCase();
  }

};

Element.Properties.html = (function()
{
  var wrapper = document.createElement('div');

  var translations = {
    table: [1, '<table>', '</table>'],
    select: [1, '<select>', '</select>'],
    tbody: [2, '<table><tbody>', '</tbody></table>'],
    tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
  };
  translations.thead = translations.tfoot = translations.tbody;

  var html = {
    set: function()
    {
      var html = Array.flatten(arguments).join('');
      var wrap = Browser.Engine.trident && translations[this.get('tag')];
      if (wrap)
      {
        var first = wrapper;
        first.innerHTML = wrap[1] + html + wrap[2];
        for (var i = wrap[0]; i--; ) first = first.firstChild;
        this.empty().adopt(first.childNodes);
      } else
      {
        this.innerHTML = html;
      }
    }
  };

  html.erase = html.set;

  return html;
})();

if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
  get: function()
  {
    if (this.innerText) return this.innerText;
    var temp = this.ownerDocument.newElement('div', { html: this.innerHTML }).inject(this.ownerDocument.body);
    var text = temp.innerText;
    temp.destroy();
    return text;
  }
};


/*
---

script: Element.Event.js

description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.

license: MIT-style license.

requires: 
- /Element
- /Event

provides: [Element.Event]

...
*/

Element.Properties.events = { set: function(events)
{
  this.addEvents(events);
} 
};

Native.implement([Element, Window, Document], {

  addEvent: function(type, fn)
  {
    var events = this.retrieve('events', {});
    events[type] = events[type] || { 'keys': [], 'values': [] };
    if (events[type].keys.contains(fn)) return this;
    events[type].keys.push(fn);
    var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
    if (custom)
    {
      if (custom.onAdd) custom.onAdd.call(this, fn);
      if (custom.condition)
      {
        condition = function(event)
        {
          if (custom.condition.call(this, event)) return fn.call(this, event);
          return true;
        };
      }
      realType = custom.base || realType;
    }
    var defn = function()
    {
      return fn.call(self);
    };
    var nativeEvent = Element.NativeEvents[realType];
    if (nativeEvent)
    {
      if (nativeEvent == 2)
      {
        defn = function(event)
        {
          event = new Event(event, self.getWindow());
          if (condition.call(self, event) === false) event.stop();
        };
      }
      this.addListener(realType, defn);
    }
    events[type].values.push(defn);
    return this;
  },

  removeEvent: function(type, fn)
  {
    var events = this.retrieve('events');
    if (!events || !events[type]) return this;
    var pos = events[type].keys.indexOf(fn);
    if (pos == -1) return this;
    events[type].keys.splice(pos, 1);
    var value = events[type].values.splice(pos, 1)[0];
    var custom = Element.Events.get(type);
    if (custom)
    {
      if (custom.onRemove) custom.onRemove.call(this, fn);
      type = custom.base || type;
    }
    return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
  },

  addEvents: function(events)
  {
    for (var event in events) this.addEvent(event, events[event]);
    return this;
  },

  removeEvents: function(events)
  {
    var type;
    if ($type(events) == 'object')
    {
      for (type in events) this.removeEvent(type, events[type]);
      return this;
    }
    var attached = this.retrieve('events');
    if (!attached) return this;
    if (!events)
    {
      for (type in attached) this.removeEvents(type);
      this.eliminate('events');
    } else if (attached[events])
    {
      while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]);
      attached[events] = null;
    }
    return this;
  },

  fireEvent: function(type, args, delay)
  {
    var events = this.retrieve('events');
    if (!events || !events[type]) return this;
    events[type].keys.each(function(fn)
    {
      fn.create({ 'bind': this, 'delay': delay, 'arguments': args })();
    }, this);
    return this;
  },

  cloneEvents: function(from, type)
  {
    from = document.id(from);
    var fevents = from.retrieve('events');
    if (!fevents) return this;
    if (!type)
    {
      for (var evType in fevents) this.cloneEvents(from, evType);
    } else if (fevents[type])
    {
      fevents[type].keys.each(function(fn)
      {
        this.addEvent(type, fn);
      }, this);
    }
    return this;
  }

});

Element.NativeEvents = {
  click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
  mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
  mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
  keydown: 2, keypress: 2, keyup: 2, //keyboard
  focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
  load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
  error: 1, abort: 1, scroll: 1 //misc
};

(function()
{

  var $check = function(event)
  {
    var related = event.relatedTarget;
    if (related == undefined) return true;
    if (related === false) return false;
    return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
  };

  Element.Events = new Hash({

    mouseenter: {
      base: 'mouseover',
      condition: $check
    },

    mouseleave: {
      base: 'mouseout',
      condition: $check
    },

    mousewheel: {
      base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'
    }

  });

})();


/*
---

script: DomReady.js

description: Contains the custom event domready.

license: MIT-style license.

requires:
- /Element.Event

provides: [DomReady]

...
*/

Element.Events.domready = {

  onAdd: function(fn)
  {
    if (Browser.loaded) fn.call(this);
  }

};

(function()
{

  var domready = function()
  {
    if (Browser.loaded) return;
    Browser.loaded = true;
    window.fireEvent('domready');
    document.fireEvent('domready');
  };

  window.addEvent('load', domready);

  if (Browser.Engine.trident)
  {
    var temp = document.createElement('div');
    (function()
    {
      ($try(function()
      {
        temp.doScroll(); // Technique by Diego Perini
        return document.id(temp).inject(document.body).set('html', 'temp').dispose();
      })) ? domready() : arguments.callee.delay(50);
    })();
  } else if (Browser.Engine.webkit && Browser.Engine.version < 525)
  {
    (function()
    {
      (['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
    })();
  } else
  {
    document.addEvent('DOMContentLoaded', domready);
  }

})();


/*
---

script: JSON.js

description: JSON encoder and decoder.

license: MIT-style license.

See Also: <http://www.json.org/>

requires:
- /Array
- /String
- /Number
- /Function
- /Hash

provides: [JSON]

...
*/

var JSON = new Hash(this.JSON && {
  stringify: JSON.stringify,
  parse: JSON.parse
}).extend({

  $specialChars: { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' },

  $replaceChars: function(chr)
  {
    return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
  },

  encode: function(obj)
  {
    switch ($type(obj))
    {
      case 'string':
        return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
      case 'array':
        return '[' + String(obj.map(JSON.encode).clean()) + ']';
      case 'object': case 'hash':
        var string = [];
        Hash.each(obj, function(value, key)
        {
          var json = JSON.encode(value);
          if (json) string.push(JSON.encode(key) + ':' + json);
        });
        return '{' + string + '}';
      case 'number': case 'boolean': return String(obj);
      case false: return 'null';
    }
    return null;
  },

  decode: function(string, secure)
  {
    if ($type(string) != 'string' || !string.length) return null;
    if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
    return eval('(' + string + ')');
  }

});

Native.implement([Hash, Array, String, Number], {

  toJSON: function()
  {
    return JSON.encode(this);
  }

});